package JDK常用类库.stream;

import org.junit.Test;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Author：russ
 * @Date：2023/8/14
 * @apiNote：
 */
public class StreamAPITest2 {
    /**
     * Stream结果收集
     */
    @Test
    public void test1() {
        /**
         * 结果收集到集合中
         */
        // Stream<String> stream = Stream.of("aa", "bb", "cc");
        List<String> list = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toList());
        System.out.println(list);
        // 收集到 Set集合中
        Set<String> set = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toSet());
        System.out.println(set);
        // 如果需要获取的类型为具体的实现，比如：ArrayList HashSet

        ArrayList<String> arrayList = Stream.of("aa", "bb", "cc", "aa")
                //.collect(Collectors.toCollection(() -> new ArrayList<>()));
                .collect(Collectors.toCollection(ArrayList::new));
        System.out.println(arrayList);

        HashSet<String> hashSet = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toCollection(HashSet::new));
        System.out.println(hashSet);

        /**
         * 结果收集到数组中
         */
        Object[] objects = Stream.of("aa", "bb", "cc", "aa")
                .toArray(); // 返回的数组中的元素是 Object类型
        System.out.println(Arrays.toString(objects));
        // 如果我们需要指定返回的数组中的元素类型
        String[] strings = Stream.of("aa", "bb", "cc", "aa")
                .toArray(String[]::new);
        System.out.println(Arrays.toString(strings));
    }

    /**
     * 对流中的数据做聚合计算
     * <p>
     * 当使用Stream流处理数据后，可以像数据库的聚合函数一样对某个字段进行操作;比如获得最大值，最小值，求和，平均值，统计数量。
     */
    @Test
    public void test2() {
        // 获取年龄的最大值
        Optional<Person> maxAge = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 15),
                new Person("张三", 19)
        ).collect(Collectors.maxBy((p1, p2) -> p1.getAge() - p2.getAge()));
        System.out.println("最大年龄：" + maxAge.get());
        // 获取年龄的最小值
        Optional<Person> minAge = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 15),
                new Person("张三", 19)
        ).collect(Collectors.minBy((p1, p2) -> p1.getAge() - p2.getAge()));
        System.out.println("最新年龄:" + minAge.get());
        // 求所有人的年龄之和
        Integer sumAge = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 15),
                new Person("张三", 19)
        )
        //.collect(Collectors.summingInt(s -> s.getAge()))
        .collect(Collectors.summingInt(Person::getAge));
        System.out.println("年龄总和：" + sumAge);
        // 年龄的平均值
        Double avgAge = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 15),
                new Person("张三", 19)
        ).collect(Collectors.averagingInt(Person::getAge));
        System.out.println("年龄的平均值：" + avgAge);
        // 统计数量
        Long count = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 15),
                new Person("张三", 19)
        ).filter(p -> p.getAge() > 18)
        .collect(Collectors.counting());
        System.out.println("满足条件的记录数:" + count);
    }

    /**
     * 对流中数据做分组操作
     */
    @Test
    public void test3() {
        /**
         * 当使用Stream流处理数据后，可以根据某个属性将数据分组。
         */
        // 根据账号对数据进行分组
        Map<String, List<Person>> map1 = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 15),
                new Person("张三", 19)
        ).collect(Collectors.groupingBy(Person::getName));
        map1.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));
        System.out.println("-----------");
        // 根据年龄分组 如果大于等于18 成年否则未成年
        Map<String, List<Person>> map2 = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 15),
                new Person("张三", 19)
        ).collect(Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年"));
        map2.forEach((k, v) -> System.out.println("k=" + k + "\t" + "v=" + v));

        /**
         * 多级分组: 先根据name分组然后根据年龄分组。
         */
        // 先根据name分组，然后根据age(成年和未成年)分组
        Map<String, Map<Object, List<Person>>> map = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 15),
                new Person("张三", 19)
        ).collect(Collectors.groupingBy(
                Person::getName
                , Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年"
                )
        ));
        map.forEach((k, v) -> {
            System.out.println(k);
            v.forEach((k1, v1) -> {
                System.out.println("\t" + k1 + "=" + v1);
            });
        });
    }

    /**
     * 对流中的数据做分区操作
     * Collectors.partitioningBy会根据值是否为true,把集合中的数据分割为两个列表，一个true列表，一个false列表。
     */
    @Test
    public void test4() {
        Map<Boolean, List<Person>> map = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 15),
                new Person("张三", 19)
        ).collect(Collectors.partitioningBy(p -> p.getAge() > 18));
        map.forEach((k, v) -> System.out.println(k + "\t" + v));
    }

    /**
     * 对流中的数据做拼接
     * Collectors.joining会根据指定的连接符，将所有的元素连接成一个字符串。
     */
    @Test
    public void test5() {
        String s1 = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 15),
                new Person("张三", 19)
        ).map(Person::getName)
        .collect(Collectors.joining());
        // 张三李四张三李四张三
        System.out.println(s1);

        String s2 = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 15),
                new Person("张三", 19)
        ).map(Person::getName)
        .collect(Collectors.joining("_"));
        // 张三_李四_张三_李四_张三
        System.out.println(s2);

        String s3 = Stream.of(
                new Person("张三", 18),
                new Person("李四", 22),
                new Person("张三", 13),
                new Person("王五", 15),
                new Person("张三", 19)
        ).map(Person::getName)
        .collect(Collectors.joining("_", "###", "$$$"));
        // ###张三_李四_张三_李四_张三$$$
        System.out.println(s3);
    }

    /**
     * 按照Map的键进行排序
     *
     * HashMap的merge()函数：
     *    1.参数一：向map里面put的键
     *    2.参数二：向map里面put的值
     *    3.参数三：如果键发生重复，如何处理值。可以是一个函数，也可以写成lambda表达式。
     *    eg：
     *    HashMap<String, Integer> map = new HashMap<String, Integer>() {{
     *          put(k, 1);
     *    }};
     *    map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal)
     *    创建了一个HashMap，并往里面放入了一个键值为k:1的元素。
     *    当调用merge函数，往map里面放入k:2键值对的时候，k键发生重复，就执行后面的lambda表达式。
     *    表达式的含义是：返回旧值oldVal加上新值newVal(1+2)，现在map里面只有一项元素那就是k:3。
     */
    @Test
    public void test6() {
        HashMap<Integer,Integer> map = new HashMap<>();
        Map<Integer, Integer> sortedMap = map.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                //.sorted(Map.Entry.<Integer,Integer>comparingByKey().reversed())//逆序：这他妈是个啥写法？？？？？？
                .collect(
                        Collectors.toMap(
                                Map.Entry::getKey,
                                Map.Entry::getValue,
                                (oldVal, newVal) -> oldVal,
                                LinkedHashMap::new
                        )
                );
        // 将排序后的Map打印
        sortedMap.entrySet().forEach(System.out::println);
        /**
         * 上述代码注意点：
         * 首先使用entrySet().stream() 将Map类型转换为Stream流类型。
         * 然后使用sorted方法排序，排序的依据是Map.Entry.comparingByKey()，也就是按照Map的键排序
         * 最后用collect方法将Stream流转成LinkedHashMap。
         * 重点看第三个参数，就是一个merge规则的lambda表达式，与merge方法的第三个参数的用法一致。
         * 由于本例中没有重复的key，所以新值旧值随便返回一个即可。
         *
         * 注意使用LinkedHashMap来存储排序的结果以保持顺序。默认情况下，Collectors.toMap()返回HashMap。HashMap不能保证元素的顺序。
         */
    }

    /**
     * 按照Map的值进行排序
     */
    @Test
    public void test7() {
        int[] nums = new int[]{1,2,3,5,7,8,1,2,3,4,5,3,2,1,1,1,1,2,3,3};
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int i = 0;i < nums.length;i++){
            map.put(nums[i],map.getOrDefault(nums[i],0) + 1);
        }
        Map<Integer, Integer> sortedMap = map.entrySet().stream()
                .sorted(Map.Entry.comparingByValue())
                //.sorted(Map.Entry.<Integer,Integer>comparingByValue().reversed())//逆序：这他妈是个啥写法？？？？？？
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (oldVal, newVal) -> oldVal,
                        LinkedHashMap::new));
        sortedMap.entrySet().forEach(System.out::println);

        List<Map.Entry<Integer,Integer>> entries = new ArrayList<>(map.entrySet());
        Collections.sort(entries, new Comparator<Map.Entry<Integer, Integer>>() {
            @Override
            public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
                return o2.getValue() - o1.getValue();
            }
        });
    }
    public int[] topKFrequent(int[] nums, int k) {
        int[] res = new int[k];
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int i = 0;i < nums.length;i++){
            map.put(nums[i],map.getOrDefault(nums[i],0) + 1);
        }
        List<Map.Entry<Integer,Integer>> entries = new ArrayList<>(map.entrySet());
        Collections.sort(entries, new Comparator<Map.Entry<Integer, Integer>>() {
            @Override
            public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
                return o2.getValue() - o1.getValue();
            }
        });
        for(int i = 0;i < k;i++){
            res[i] = entries.get(i).getKey();
        }
        return res;
    }
}

